#include "StdAfx.h"
#include "FilterList.h"
#include <fstream>
#include <boost/algorithm/string.hpp>
#include "utils.h"
#include <memory>

using namespace std;

CFilterList::CFilterList(void)
	: SHORTCUTS_SIZE(8)
{
	_elementHidingCollection = make_shared<CElementHidingCollection>();
}


CFilterList::~CFilterList(void)
{
}

std::unordered_map<wstring, CSerializableRegex>& CFilterList::GetFiltersWithShortcuts()
{
	return _filtersWithShortcuts;
}

std::vector<CSerializableRegex>& CFilterList::GetFiltersWithoutShourtcuts()
{
	return _filtersWithoutShourtcuts;
}



void CFilterList::InitializeFromAdblockPlusFiltersFile(string path)
{
	ifstream file;	

	file.open(path);
	if(file.is_open())
	{		
		InitializeFromAdblockPlusFiltersStream(file);
	}   
	file.close();
}

void CFilterList::InitializeFromAdblockPlusFiltersStream(istream& stream)
{
	string line;

	while(getline(stream,line))
	{
		try
		{
			FixNewLines(line);

			if( line.empty() || IsRuleComment(line) )
				continue;		
			if(boost::starts_with(line,"[Adblock Plus"))	//fist line //TODO:implement it different so it  won't be checked for every line)
				continue;
								
			bool isHidingRule = CheckForElementHidingRuleAndSaveIt(line);
			if(isHidingRule)
				continue;

			CSerializableRegex filter = TransformAdblockFilterToRegularExpressions(line);

			CreateShortcutsIfPossible(line, filter);	
		}
		catch(exception &e)
		{
			//..if error occur during parsing line
			//then we are skipping this line and continue with others
		}
	}
}


bool CFilterList::IsRuleComment(const string& rule)
{
	return (*rule.begin() == '!');
}

void CFilterList::FixNewLines(string& rule)
{
	if ( (!rule.empty()) && (*rule.rbegin() == '\r') )
	{
		//erasing \r if new lines are in windows style (\r\n)
		rule.erase(rule.end() - 1);	
	}
}

bool CFilterList::CheckForElementHidingRuleAndSaveIt(const string& rule)
{
	string domain;
	string selector;

	auto pos = rule.find("##");
	if(pos != string::npos)
	{
		domain =  string(rule.begin(), rule.begin() + pos);
		selector = string(rule.begin() + pos + 2, rule.end());

		return _elementHidingCollection->Add(domain, selector);
	}
	else
		return false;    
}


CSerializableRegex CFilterList::TransformAdblockFilterToRegularExpressions(const string &filter)
{
	if(boost::starts_with(filter,"nyaa.eu##div[style*=") && boost::ends_with(filter, "src$=\".png\"])"))
	{
		int a = 5;
	}
	string rule = filter;

	bool removedBegChar = false;
	bool removedEndChar = false;
	RemoveFilterBeginningEndingCharacters(rule, removedBegChar, removedEndChar);

	ChangeFilterCharactersToRegexSpecialCharacters(rule);	

	if(removedBegChar)
		rule.insert(rule.begin(),'^');	//adding regex character for matching beginning
	if(removedEndChar)
		rule += '&';	//adding regex character for matching ending

	wstring wrule = toStdWString(rule);
	wregex regexFilter(wrule, regex_constants::icase | regex_constants::optimize);

	return CSerializableRegex(regexFilter, wrule);
}

void CFilterList::ChangeFilterCharactersToRegexSpecialCharacters(string& filter)
{
	for(string::iterator it = filter.begin(); it != filter.end(); ++it)
	{
		switch(*it)
		{
		case '*':
			it = filter.insert(it,'.');
			++it;
			break;
		case '+': case '.': case '?':
		case '|': case '[': case ']':
		case '(': case ')':
		case '\\':
			it = filter.insert(it,'\\');
			++it;
			break;
		case '$': //adblock filter options
			filter.erase(it, filter.end());	//TODO:Implement this feature. Right now we are ignoring this options.
			--it;
			//after erasing we are decrementing iterator to don't 
			//skip character (because of incrementing in for loops)
			break;
		case '^':
			//this is temporary solution thar replace "^" with ".*"
			//TODO: imlement so that will be replaced by actual seperating characters
			it = filter.insert(it,'.');
			++it;
			it = filter.insert(it,'*');
			++it;
			it = filter.erase(it);
			--it;
			break;		
		}
	}
}

void CFilterList::RemoveFilterBeginningEndingCharacters(string& filter, bool& matchedToBeginning, bool& matchedToEnding)
{
	if(*filter.begin() == '|')	//if filter must match at the beginning 
	{
		filter.erase(filter.begin());
		if(*filter.begin() != '|') //and there is jus "|" not "||" (different meaning, //TODO: implement it later)
		{
			//filter.insert(filter.begin(),'^');
			matchedToBeginning = true;
		}
		else //there is second '|'
		{
			filter.erase(filter.begin());
		}							
	}

	if(*filter.rbegin() == '|') //if rule must match at the end
	{
		filter.erase(filter.end()-1);
		//filter += '$';
		matchedToEnding = false;
	}
}

void CFilterList::CreateShortcutsIfPossible(const string& line, const CSerializableRegex& filter)
{
	//we are creating shortcuts 8 lenght string for 
	//quickly accesing to filter(regex) in unordered_map

	wstring shortcut;
	bool hasUniqueShortcut = GetShortcutForFilter(line, shortcut);
	if(hasUniqueShortcut)
	{
		//inserting filter to hashtable
		_filtersWithShortcuts[shortcut] = filter;
	}
	else
	{
		_filtersWithoutShourtcuts.push_back(filter);
	}
}

bool CFilterList::GetShortcutForFilter(const string& filter, wstring& shortcut)
{
	if(filter.size() < SHORTCUTS_SIZE)
		return false;
	
	string clearedFilter = filter;
	RemoveCharactersForMatchinBeginningAndEndOfAddress(clearedFilter);
	//transform(clearedFilter.begin(), clearedFilter.end(), clearedFilter.begin(), tolower);
	boost::to_lower(clearedFilter);

	size_t foundCharIndex;
	//'from' and 'to' define 'frame' that will be moving throught the filter string
	//we will check if that frame (substring) is a valid shortcut and that this shortcut
	//doesn't already exits for other filter
	auto from = clearedFilter.begin();
	auto to = clearedFilter.begin() + SHORTCUTS_SIZE;

	while(true)
	{
		string sub(from, to);

		foundCharIndex = sub.find_first_of('*');

		if(foundCharIndex != string::npos)
		{			
			from += foundCharIndex + 1;
			size_t range = distance(from, clearedFilter.end());
			if(range >= SHORTCUTS_SIZE)
			{
				to += foundCharIndex + 1;
			}
			else
			{
				return false;	//there are no shortcuts for filter
			}

			continue;	//continue searching for shortcut
		}

		wstring wsub = toStdWString(sub);
		if(_filtersWithShortcuts.count(wsub) == 0)
		{
			shortcut = wsub;
			return true;
		}
		else if(to == clearedFilter.end())	//if it was last possible filter
		{
			return false;
		}
		else	//move "frame" to next position
		{
			++from;
			++to;
		}
	}
}

void CFilterList::RemoveCharactersForMatchinBeginningAndEndOfAddress(string& filter)
{
	if(*filter.rbegin() == '|')	
	{
		filter.erase(filter.end()-1);
	}
	if(*filter.begin() == '|')
	{
		filter.erase(filter.begin());
		if(*filter.begin() == '|')
		{
			filter.erase(filter.begin());
		}						
	}
}